package core.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import core.util.ErrorList;

public abstract class ServerHandler<T>{

//	private boolean isReceive = true;
	private boolean isReceive = true;
	private Socket clientSocket;
	private OutputStream os = null;
	private InputStream is = null;
	private long timeWait = 0;
	private long startTime = 0;
	private CheckIdleServer cis;
	private Logger logE = LoggerFactory.getLogger("LogError");
	
	
	public ServerHandler(Socket clientSocket) {
		this.clientSocket = clientSocket;
	}

	public abstract byte[] encode(Object t);

	public abstract T decode();

	/**
	 * Xu ly su kien nha du lieu
	 */
	public abstract void onReceive(T t);

	/**
	 * Cai dat cau hinh
	 */
	public abstract void onConfig();

	/**
	 * Xu ly suj kien khi co loi
	 * 
	 * @param e
	 */
	public abstract void onError(Exception e, String errorType);

	/**
	 * Xu ly su kien cho doi trong thoi gian dai
	 */
	public abstract void onIdle();

	public abstract void onRun();

	public void config() {
		onConfig();
	}

	public void send(Object t) {
		byte[] bt = encode(t);
		if (bt == null) {
			return;
		}
		sendByte(os, bt);
	}

	public void receive() {
		T t = decode();
		if (t == null) {
			return;
		}
		onReceive(t);
		freeMemory();
	}

	public void run() {
		config();
//		runIdle();
		ServerReceive serverReceive = new ServerReceive(this);
		serverReceive.start();

	}

	/**
	 * Gui du lieu cho client
	 * 
	 * @param bt
	 */
	public void sendByte(OutputStream os, byte[] bt) {
		if (os == null) {
			try {
				os = clientSocket.getOutputStream();
			} catch (Exception e) {
				onError(e, ErrorList.E_IOSTREAM);
			}
		}
		try {
			os.write(bt);
			os.flush();
		} catch (IOException e) {
			onError(e, ErrorList.E_WRITE);
		}
	}

	/**
	 * 
	 * @return InputStream
	 */
	public InputStream getInputStream() {
		if (is == null) {
			try {
				is = clientSocket.getInputStream();
			} catch (IOException e) {
				onError(e, ErrorList.E_IOSTREAM);
			}
		}
		return is;
	}

	/**
	 * 
	 * @return OutputStream
	 */
	public OutputStream getOutputStream() {
		if (os == null) {
			try {
				os = clientSocket.getOutputStream();
			} catch (IOException e) {
				onError(e, ErrorList.E_IOSTREAM);
			}
		}
		return os;
	}

	public void setTimeIdle(long timeWait) {
		this.timeWait = timeWait;
	}

	public void stopReceive() {
		isReceive = false;
	}

	public void sendTo(Socket desSocket, byte[] bt) {
		OutputStream os = null;
		try {
			os = desSocket.getOutputStream();
			os.write(bt);
			os.flush();
		} catch (IOException e) {
			onError(e, ErrorList.E_WRITE);
		}
	}

	public void stopCheckIdle() {
		if (cis != null) {
			cis.stopCheckIdle();
		}
	}

	public void closeSocket() {
		stopCheckIdle();
		stopReceive();
		try {
			clientSocket.close();
		} catch (IOException e) {
			onError(e, ErrorList.E_CLOSESOCKET);
		}
	}
	
	public byte[] intByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		int value = ByteBuffer.wrap(inByte).getInt();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putInt(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	public byte[] longByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		long value = ByteBuffer.wrap(inByte).getLong();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putLong(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	public byte[] floatByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		float value = ByteBuffer.wrap(inByte).getFloat();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putFloat(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}

	public byte[] doubleByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		double value = ByteBuffer.wrap(inByte).getDouble();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putDouble(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
	
	public byte[] charByteOrder(byte[] inByte, ByteOrder byteOrder){
		int leng = inByte.length;
		char value = ByteBuffer.wrap(inByte).getChar();
		byte[] bt = null;
		ByteBuffer bb = ByteBuffer.allocate(leng).order(byteOrder);
		bt = new byte[leng];
		bb.putChar(value);
		bb.position(0);
		bb.get(bt);
		return bt;
	}
//	private ByteBuffer bb = ByteBuffer.allocate(3126);
	public byte[] read(InputStream is, int length){
//		ByteBuffer bb = ByteBuffer.allocate(length);
		byte[] allbt = new byte[length];
//		byte[] zxc = allbt.clone();
//		bb.clear();
//		bb.position(0);
		if (is == null) {
			return null;
		}
		int realRead = 0;
		int totalRead = 0;
//		int position = 0;
		byte[] bt = null;//new byte[0];
//		int i = 0;
		try {
//			logE.debug("Start read==>totalRead: " + totalRead + "|length: " + length + " | " + i);
			while(totalRead < length) {
				bt = new byte[length - totalRead];
				realRead = is.read(bt);
				if (realRead < 0) {
//					logE.debug("break read==>totalRead: " + totalRead + "|length: " + length + " | " + i);
					return null;
				}
//				bb.put(bt, 0, realRead);
				System.arraycopy(bt, 0, allbt, totalRead, realRead);
				totalRead += realRead;
				
//				i++;
			
//				logE.debug("totalRead: " + totalRead + "|length: " + length + " | " + i);
			}
		} catch (Exception e) {
			logE.error("length: " + length + " | byte size: " + bt.length + " | totalRead" + totalRead + " | realRead: " + realRead, e);
			onError(e, ErrorList.E_READ);
		}
		
//		bb.position(0);
//		bb.rewind();
//		bt = bb.array();
//		bb.clear();
//		bb = null;
		freeMemory();
		return allbt;
	}
	
	public byte[] read2(InputStream is, int leng){
		byte[] bt = new byte[0];
		if (is == null) {
			return null;
		}
		
		
		return null;
	}
	
	protected boolean isReceive(){
		return isReceive;
	}
	
	protected void resetStartTime(){
		startTime = System.currentTimeMillis();
	}
	
	protected synchronized boolean isWait() {
		long time = System.currentTimeMillis() - startTime;
		if (time > timeWait) {
			return false;
		}
		return true;
	}

	private void runIdle() {
		if (timeWait <= 0) {
			return;
		}
		cis = new CheckIdleServer(this, timeWait);
		cis.start();
	}
	
	public void freeMemory(){
		Runtime rt = Runtime.getRuntime();
		rt.gc();
	}
	
}
